package com.symaster.common.animation.nest4j.util;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.symaster.common.animation.nest4j.data.*;
import com.symaster.common.animation.nest4j.util.coor.ClipperCoor;
import com.symaster.common.clipper.*;
import com.symaster.common.config.AppConfig;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;


/**
 * @author yisa
 */
public class Placementworker {
    private static final Gson gson = new GsonBuilder().create();
    public NestPath binPolygon;
    public Config config;
    public Map<String, List<NestPath>> nfpCache;

    /**
     * @param binPolygon 底板参数
     * @param config     设置
     * @param nfpCache   nfp列表
     */
    public Placementworker(NestPath binPolygon, Config config, Map<String, List<NestPath>> nfpCache) {
        this.binPolygon = binPolygon;
        this.config = config;
        this.nfpCache = nfpCache;
    }

    /**
     * 坐标转换，与clipper库交互必须坐标转换
     */
    public static Path scaleUp2ClipperCoordinates(NestPath polygon) {
        Path p = new Path();
        for (Segment s : polygon.getSegments()) {
            ClipperCoor cc = CommonUtil.toClipperCoor(s.x, s.y);
            p.add(new Point.LongPoint(cc.getX(), cc.getY()));
        }
        return p;
    }

    public static NestPath toNestCoordinates(Path polygon) {
        NestPath clone = new NestPath();
        for (Point.LongPoint longPoint : polygon) {
            Segment s = new Segment((double) longPoint.getX() / AppConfig.CLIIPER_SCALE, (double) longPoint.getY() / AppConfig.CLIIPER_SCALE);
            clone.add(s);
        }
        return clone;
    }

    /**
     * 根据板件列表与旋转角列表，通过nfp,计算板件在底板上的位置，并返回这个种群的fitness
     */
    public Result placePaths(List<NestPath> paths) {
        List<NestPath> rotated = new ArrayList<>();
        for (NestPath value : paths) {
            NestPath r = GeometryUtil.rotatePolygon2Polygon(value, value.getRotation());
            r.setRotation(value.getRotation());
            r.setSource(value.getSource());
            r.setId(value.getId());
            rotated.add(r);
        }
        paths = rotated;

        List<List<Vector>> allPlacements = new ArrayList<>();
        double fitness = 0;
        double binArea = Math.abs(GeometryUtil.polygonArea(this.binPolygon));
        String key;
        List<NestPath> nfp;

        while (paths.size() > 0) {

            List<NestPath> placed = new ArrayList<>();
            List<Vector> placements = new ArrayList<>();

            fitness += 1;
            double minwidth = Double.MAX_VALUE;
            for (NestPath path : paths) {

                // inner NFP
                key = gson.toJson(new NfpKey(-1, path.getId(), true, 0, path.getRotation()));

                if (!nfpCache.containsKey(key)) {
                    continue;
                }

                List<NestPath> binNfp = nfpCache.get(key);


                // ensure exists
                boolean error = false;
                for (NestPath nestPath : placed) {
                    key = gson.toJson(new NfpKey(nestPath.getId(), path.getId(), false, nestPath.getRotation(), path.getRotation()));
                    if (nfpCache.containsKey(key)) {
                        nfp = nfpCache.get(key);
                    } else {
                        error = true;
                        break;
                    }
                }
                if (error) {
                    continue;
                }


                Vector position = null;
                if (placed.size() == 0) {
                    // first placement , put it on the lefth
                    for (NestPath nestPath : binNfp) {
                        for (int k = 0; k < nestPath.size(); k++) {
                            if (position == null || nestPath.get(k).x - path.get(0).x < position.x) {
                                position = new Vector(
                                        nestPath.get(k).x - path.get(0).x,
                                        nestPath.get(k).y - path.get(0).y,
                                        path.getId(),
                                        path.getRotation()
                                );
                            }
                        }
                    }
                    placements.add(position);
                    placed.add(path);
                    continue;
                }

                Paths clipperBinNfp = new Paths();

                for (NestPath binNfpJ : binNfp) {
                    clipperBinNfp.add(scaleUp2ClipperCoordinates(binNfpJ));
                }
                DefaultClipper clipper = new DefaultClipper();
                Paths combinedNfp = new Paths();


                for (int j = 0; j < placed.size(); j++) {
                    key = gson.toJson(new NfpKey(placed.get(j).getId(), path.getId(), false, placed.get(j).getRotation(), path.getRotation()));
                    nfp = nfpCache.get(key);
                    if (nfp == null) {
                        continue;
                    }

                    for (NestPath nestPath : nfp) {
                        Path clone = scaleUp2ClipperCoordinates(nestPath);
                        for (Point.LongPoint longPoint : clone) {
                            long clx = longPoint.getX();
                            long cly = longPoint.getY();
                            longPoint.setX(clx + (long) (placements.get(j).x * AppConfig.CLIIPER_SCALE));
                            longPoint.setY(cly + (long) (placements.get(j).y * AppConfig.CLIIPER_SCALE));
                        }
                        clone = clone.cleanPolygon(0.0001 * AppConfig.CLIIPER_SCALE);
                        double area = Math.abs(clone.area());
                        if (clone.size() > 2 && area > 0.1 * AppConfig.CLIIPER_SCALE * AppConfig.CLIIPER_SCALE) {
                            clipper.addPath(clone, Clipper.PolyType.SUBJECT, true);
                        }
                    }
                }
                if (!clipper.execute(Clipper.ClipType.UNION, combinedNfp, Clipper.PolyFillType.NON_ZERO, Clipper.PolyFillType.NON_ZERO)) {
                    continue;
                }

                // difference with bin polygon
                Paths finalNfp = new Paths();
                clipper = new DefaultClipper();

                clipper.addPaths(combinedNfp, Clipper.PolyType.CLIP, true);
                clipper.addPaths(clipperBinNfp, Clipper.PolyType.SUBJECT, true);
                if (!clipper.execute(Clipper.ClipType.DIFFERENCE, finalNfp, Clipper.PolyFillType.NON_ZERO, Clipper.PolyFillType.NON_ZERO)) {
                    continue;
                }

                finalNfp = finalNfp.cleanPolygons(0.0001 * AppConfig.CLIIPER_SCALE);
                for (int j = 0; j < finalNfp.size(); j++) {
                    double area = Math.abs(finalNfp.get(j).area());
                    if (finalNfp.get(j).size() < 3 || area < 0.1 * AppConfig.CLIIPER_SCALE * AppConfig.CLIIPER_SCALE) {
                        finalNfp.remove(j);
                        j--;
                    }
                }

                if (finalNfp.size() == 0) {
                    continue;
                }

                List<NestPath> f = new ArrayList<>();
                for (Path longPoints : finalNfp) {
                    f.add(toNestCoordinates(longPoints));
                }

                NestPath nf;
                double area;
                Vector shifvector;
                double minarea = Double.MIN_VALUE;
                double minX = Double.MAX_VALUE;
                for (NestPath nestPath : f) {
                    nf = nestPath;
                    if (Math.abs(GeometryUtil.polygonArea(nf)) < 2) {
                        continue;
                    }
                    for (int k = 0; k < nf.size(); k++) {
                        NestPath allPoints = new NestPath();
                        for (int m = 0; m < placed.size(); m++) {
                            for (int n = 0; n < placed.get(m).size(); n++) {
                                allPoints.add(new Segment(placed.get(m).get(n).x + placements.get(m).x,
                                                          placed.get(m).get(n).y + placements.get(m).y));
                            }
                        }
                        shifvector = new Vector(
                                nf.get(k).x - path.get(0).x,
                                nf.get(k).y - path.get(0).y,
                                path.getId(),
                                path.getRotation(),
                                combinedNfp
                        );
                        for (int m = 0; m < path.size(); m++) {
                            allPoints.add(new Segment(path.get(m).x + shifvector.x, path.get(m).y + shifvector.y));
                        }
                        Bound rectBounds = GeometryUtil.getPolygonBounds(allPoints);

                        area = rectBounds.getWidth() * 2 + rectBounds.getHeight();
                        if (minarea == Double.MIN_VALUE
                                || area < minarea
                                || (GeometryUtil.almostEqual(minarea, area)
                                && (minX == Double.MIN_VALUE || shifvector.x < minX))) {
                            minarea = area;
                            minwidth = rectBounds.getWidth();
                            position = shifvector;
                            minX = shifvector.x;
                        }
                    }
                }
                if (position != null) {

                    placed.add(path);
                    placements.add(position);
                }
            }
            if (minwidth != Double.MAX_VALUE) {
                fitness += minwidth / binArea;
            }


            for (NestPath nestPath : placed) {
                int index = paths.indexOf(nestPath);
                if (index >= 0) {
                    paths.remove(index);
                }
            }

            if (placements.size() > 0) {
                allPlacements.add(placements);
            } else {
                break; // something went wrong
            }

        }
        // there were paths that couldn't be placed
        fitness += 2 * paths.size();
        return new Result(allPlacements, fitness, paths, binArea);
    }

}
